﻿/*
   Copyright 2011 Softisis

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

using System;
using System.Collections.Generic;
using FOM.Interfaces;

namespace FOM.Impl
{
    /// <summary>
    /// Abstract implementation for IMapping. Manages children objects and parent relationship.
    /// </summary>
    /// <remarks>Copyright Softisis 2011 (France)</remarks>
    public abstract class AbstractMappingNode : IComparable<AbstractMappingNode>
    {

        /// <summary>
        /// Not null if a condition have to be applied on this node. If condtion is "false" ==> no mapping on target object
        /// </summary>
        internal ICondition Condition { get; set; }

        /// <summary>
        /// Overriding target type if given.
        /// </summary>
        internal Type TargetOverrideType { get; set; }

        /// <summary>
        /// Overriding source type if given.
        /// </summary>
        internal Type SourceOverrideType { get; set; }


        /// <summary>
        /// Pathes defining the source and target, from the parent (not from the root element)
        /// </summary>
        internal IPathLink NodePathLink { get; set; }

        /// <summary>
        /// Parent path
        /// </summary>
        internal IPathLink ParentPath { get; private set; }

        /// <summary>
        /// Full path, from parent
        /// </summary>
        internal IPathLink FullPathLink
        {
            get
            {
                if (ParentPath == null)
                    return NodePathLink;

                var str1 = string.IsNullOrEmpty(ParentPath.SourcePath)
                               ? ""
                               : (string.IsNullOrEmpty(NodePathLink.SourcePath) ? "" : (ParentPath.SourcePath + "."));
                var str2 = string.IsNullOrEmpty(ParentPath.TargetPath)
                               ? ""
                               : (string.IsNullOrEmpty(NodePathLink.TargetPath) ? "" : (ParentPath.TargetPath + "."));

                str1 += NodePathLink.SourcePath;
                str2 += NodePathLink.TargetPath;

                return new PathLink(str1, str2);
            }
        }

        /// <summary>
        /// Tells the engine to reuse 
        /// </summary>
        internal bool ReuseCachedInstance;


        /// <summary>
        /// Source type
        /// </summary>
        internal Type SourceType;

        /// <summary>
        /// target type
        /// </summary>
        internal Type TargetType;

        /// <summary>
        /// True if create new instance
        /// </summary>
        internal bool CreateNewInstance { get; set; }

        /// <summary>
        /// Set it to use another converter.
        /// </summary>
        internal IConverter Converter { get; set; }

        /// <summary>
        /// tells the engine if it must convert before testing the condition (if exists).
        /// Default if condition then conversion.
        /// </summary>
        internal bool ConvertBeforeTest { get; set; }

        /// <summary>
        /// Children mapping list
        /// </summary>
        internal List<AbstractMappingNode> Children { get; set; }

        /// <summary>
        /// Excluded mappings
        /// </summary>
        internal List<IPathLink> Excluded { get; set; }

        /// <summary>
        /// Parent
        /// </summary>
        internal AbstractMappingNode Parent { get; set; }
        internal void AddMapping(AbstractMappingNode newOne)
        {
            if (Children == null)
                Children = new List<AbstractMappingNode>();

            Children.Add(newOne);
            newOne.Parent = this;

            string str1 = "";
            string str2 = "";
            if (Parent!=null)
            {
                IPathLink parenpl = Parent.NodePathLink;
                str1 = (string.IsNullOrEmpty(parenpl.SourcePath) ? "" : (parenpl.SourcePath + "."));
                str2 = (string.IsNullOrEmpty(parenpl.TargetPath) ? "" : (parenpl.TargetPath + "."));
            }

            var srcparent = (string.IsNullOrEmpty(str1) ? "" : (str1 + ".")) + NodePathLink.SourcePath;
            var tgtparent = (string.IsNullOrEmpty(str2) ? "" : (str2 + ".")) + NodePathLink.TargetPath;

            newOne.ParentPath = new PathLink(srcparent, tgtparent);
        }

        /// <summary>
        /// Remove a node mapping. If not exist, no error, false is returned
        /// </summary>
        /// <param name="existingOne"></param>
        internal bool RemoveMapping(AbstractMappingNode existingOne)
        {
            if (Children == null)
                return false;

            var amn = new List<AbstractMappingNode>();
            foreach (var c in Children)
            {
                if (c.FullPathLink==null || c.FullPathLink.SourcePath==null || c.FullPathLink.TargetPath==null)
                    continue;

                if (existingOne.FullPathLink==null || existingOne.FullPathLink.SourcePath==null || existingOne.FullPathLink.TargetPath==null)
                    continue;
                
                if (c.FullPathLink.SourcePath.Equals(existingOne.FullPathLink.SourcePath) && c.FullPathLink.TargetPath.Equals(existingOne.FullPathLink.TargetPath))
                    amn.Add(c);
            }

            foreach (var amn2 in amn)
                Children.Remove(amn2);

            return amn.Count > 0;
        }

        /// <summary>
        /// Builds the paths of children by appending them to the this current node path.
        /// </summary>
        /// <returns>The list of subpath. Never null because it returns at least itself</returns>
        internal List<FlatMappingDescriptor> CollectNodes(string srcbpath, string dstbpath)
        {
            List<FlatMappingDescriptor> ret = new List<FlatMappingDescriptor>();
            var srctmp = (string.IsNullOrEmpty(srcbpath) ? "" : (srcbpath + ".")) + NodePathLink.SourcePath;
            var tgttmp = (string.IsNullOrEmpty(dstbpath) ? "" : (dstbpath + ".")) + NodePathLink.TargetPath;

            // this node
            ret.Add(new FlatMappingDescriptor(this, srctmp, tgttmp));

            // and the children nodes (if exists)
            if (Children != null)
                foreach (var c in Children)
                    ret.AddRange(c.CollectNodes(srctmp, tgttmp));

            return ret;
        }

        /// <summary>
        /// Comparison purpose
        /// </summary>
        /// <param name="other"></param>
        /// <returns></returns>
        public int CompareTo(AbstractMappingNode other)
        {
            if (other == null)
                return -1;
            return NodePathLink.TargetPath.CompareTo(other.NodePathLink.TargetPath);
        }

        /// <summary>
        /// Equals implementation
        /// </summary>
        /// <param name="o"></param>
        /// <returns></returns>
        public override bool Equals(Object o)
        {
            return Equals(o as AbstractMappingNode);
        }

        public bool Equals(AbstractMappingNode other)
        {
            if (ReferenceEquals(null, other)) return false;
            if (ReferenceEquals(this, other)) return true;
            return Equals(other.TargetType, TargetType);
        }

        public override int GetHashCode()
        {
            return (TargetType != null ? TargetType.GetHashCode() : 0);
        }

        /// <summary>
        /// Add an exclusion node. This must be in the path of this object
        /// </summary>
        /// <param name="newmap"></param>
        public void AddExclusionMapping(AbstractMappingNode newmap)
        {
            // we add parent path + this path + the new exlcusion path, 
            // to have directly the full exclusion path name.
            string str1 = "";
            string str2 = "";
            if (ParentPath!=null)
            {
                str1 = string.IsNullOrEmpty(ParentPath.SourcePath) ? "" : ParentPath.SourcePath + ".";
                str2 = string.IsNullOrEmpty(ParentPath.TargetPath) ? "" : ParentPath.TargetPath + ".";
            }

            str1+=string.IsNullOrEmpty(NodePathLink.SourcePath) ? "" : (string.IsNullOrEmpty(newmap.NodePathLink.SourcePath)?"":NodePathLink.SourcePath + ".");
            str2+= string.IsNullOrEmpty(NodePathLink.TargetPath) ? "" : (string.IsNullOrEmpty(newmap.NodePathLink.TargetPath)?"":NodePathLink.TargetPath + ".");

            var fullsrc = str1 + newmap.NodePathLink.SourcePath;
            var fulltgt = str2 + newmap.NodePathLink.TargetPath;

            if (Excluded == null)
                Excluded = new List<IPathLink>();

            IPathLink fullPl = new PathLink(fullsrc, fulltgt);

            Excluded.Add(fullPl);
        }

        ///<summary>
        /// Reverse the node
        ///</summary>
        ///<returns></returns>
        public abstract AbstractMappingNode ReverseNode();

    }
}
